use fudata::round2;
use fudata::model::kline::Kline;
use fudata::model::kline::KlineValley;
use log::info;
use log::trace;
use surrealdb::sql::Thing;

use crate::qtrade::strategy::util::angle_degrees;

/// 计算简单移动平均线
pub fn calculate_sma(klines: &[Kline], period: usize) -> Vec<(String, f64)> {
    let mut smas = Vec::with_capacity(klines.len());
    let mut sum = 0.0;

    for (i, kline) in klines.iter().enumerate() {
        sum += kline.close;
        if i < period {
            smas.push((kline.id_raw(), round2(sum / ((i as f64) + 1.0))));
        } else {
            sum -= klines[i - period].close;
            smas.push((kline.id_raw(), round2(sum / (period as f64))));
        }
    }

    smas
}

/// 找到 SMA5 上穿 SMA21 的索引
pub fn find_crossover_indices(
    sma5s: &[(String, f64)],
    sma21s: &[(String, f64)]
) -> Vec<(String, usize)> {
    let mut crossovers = Vec::new();

    for i in 21..sma5s.len() {
        // skip the first max-period 21 items, as before 21 is not enough klines to compute sma21
        if
            sma5s[i].1 > sma21s[i].1 &&
            (sma5s[i - 1].1 < sma21s[i - 1].1 ||
                (sma5s[i - 1].1 == sma21s[i - 1].1 &&
                    (sma5s[i - 2].1 < sma21s[i - 2].1 ||
                        sma5s[i - 3].1 < sma21s[i - 3].1 ||
                        sma5s[i - 4].1 < sma21s[i - 4].1)))
        {
            // trace!("push crossover sma[{i}]={:?}", sma5[i]);
            crossovers.push((sma5s[i].0.clone(), i));
        }
    }

    crossovers
}

/// 找到 SMA21 下穿 SMA5 的索引
pub fn find_crossunder_indices(
    sma5s: &[(String, f64)],
    sma21s: &[(String, f64)]
) -> Vec<(String, usize)> {
    let mut crossunders = Vec::new();

    for i in 21..sma5s.len() {
        // skip the first max-period 21 items, as before 21 is not enough klines to compute sma21
        if
            sma5s[i].1 < sma21s[i].1 &&
            (sma5s[i - 1].1 > sma21s[i - 1].1 ||
                (sma5s[i - 1].1 == sma21s[i - 1].1 &&
                    (sma5s[i - 2].1 > sma21s[i - 2].1 ||
                        sma5s[i - 3].1 > sma21s[i - 3].1 ||
                        sma5s[i - 4].1 > sma21s[i - 4].1)))
        {
            crossunders.push((sma5s[i].0.clone(), i));
        }
    }

    crossunders
}

/// 在左侧找到最近的 SMA5 上穿 SMA21 的索引
fn find_nearest_crossover_left<'a>(
    crossovers: &'a [(String, usize)],
    index: &(String, usize)
) -> Option<&'a (String, usize)> {
    let mut nearest_crossover = None;
    for crossover in crossovers.iter() {
        if crossover.1 < index.1 {
            nearest_crossover = Some(crossover);
        } else {
            break;
        }
    }
    nearest_crossover
}

/// 在右侧找到最近的 SMA5 下穿 SMA21 的索引
fn find_nearest_crossunder_right<'a>(
    crossunders: &'a [(String, usize)],
    lowest_index: &(String, usize)
) -> Option<&'a (String, usize)> {
    let mut nearest_crossunder = None;
    for crossunder in crossunders.iter().rev() {
        if crossunder.1 > lowest_index.1 {
            nearest_crossunder = Some(crossunder);
        } else {
            break;
        }
    }
    nearest_crossunder
}

/// 在 SMA5 下穿 SMA21 到 SMA5 上穿 SMA21 的区间内找到最低点
///
/// is_sun_line: 是否阳线
///
pub fn find_lowest(
    all_klines: &[Kline],
    start_index: usize,
    end_index: usize,
    is_sun_line: Option<bool>
) -> Option<(String, usize)> {
    let mut min_low = f64::MAX;
    let mut min_kline_index = None;

    for i in start_index..end_index {
        let kline = &all_klines[i];
        if kline.low < min_low {
            match is_sun_line {
                Some(v) => if (v && kline.close > kline.open) || (!v && kline.close < kline.open) {
                    min_low = kline.low;
                    min_kline_index = Some((kline.id_raw(), i));
                }
                None => {
                    min_low = kline.low;
                    min_kline_index = Some((kline.id_raw(), i));
                }
            }
        }
    }

    min_kline_index
}

/// 在区间内找到最高点
pub fn find_highest(
    all_klines: &[Kline],
    start_index: usize,
    end_index: usize
) -> Option<(String, usize)> {
    let mut max_high = f64::MIN;
    let mut max_kline_index: Option<(String, usize)> = None;

    for i in start_index..end_index {
        if all_klines[i].high > max_high {
            max_high = all_klines[i].high;
            max_kline_index = Some((all_klines[i].id_raw(), i));
        }
    }

    max_kline_index
}

/// 获取所有有效的 crossunder 和 crossover 匹配对
fn get_crossunder_over_pairs<'a, 'b>(
    crossunders: &'a [(String, usize)],
    crossovers: &'b [(String, usize)]
) -> Vec<(&'a (String, usize), &'b (String, usize))> {
    let mut pairs = Vec::new();
    let mut matched_crossunders = vec![false; crossunders.len()];

    for (j, crossunder) in crossunders.iter().enumerate() {
        for (i, crossover) in crossovers.iter().enumerate() {
            if crossover.1 > crossunder.1 && !matched_crossunders[j] {
                pairs.push((crossunder, crossover));
                matched_crossunders[j] = true;
                break;
            }
        }
    }

    pairs
}

/// 查找波谷
///
/// all_klines: 按照时间顺序排列
///
/// min_threshold: valley 阀值百分比如 20,用于检测两边跌涨幅度
///
/// min_threshold: valley 阀值百分比如 20,用于检测两边跌涨幅度
///
///
pub fn find_valleys(
    all_klines: &Vec<Kline>,
    min_threshold: f64,
    max_threshold: f64
) -> Vec<KlineValley> {
    trace!("");

    let sma5s = calculate_sma(all_klines, 5);
    for (idx, ov) in sma5s.iter().enumerate() {
        if idx < 3 || idx >= sma5s.len() - 3 {
            trace!("Sma5[{idx}] {:?}", ov);
        }
    }
    trace!("");
    let sma21s = calculate_sma(all_klines, 21);
    for (idx, ov) in sma21s.iter().enumerate() {
        if idx < 3 || idx >= sma21s.len() - 3 {
            trace!("Sma21[{idx}] {:?}", ov);
        }
    }

    trace!("\ncrossovers*********");
    let crossovers = find_crossover_indices(&sma5s, &sma21s);
    // crossovers.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
    for (idx, ov) in crossovers.iter().enumerate() {
        trace!("Crossover[{idx}] {:?}", ov);
    }

    trace!("\ncrossunders*********");
    let mut crossunders = find_crossunder_indices(&sma5s, &sma21s);
    // crossunders.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));

    for (idx, ov) in crossunders.iter().enumerate() {
        trace!("Crossunders[{idx}] {:?}", ov);
    }
    trace!("\npairs*********");
    // 获取所有有效的 crossunder 和 crossover 匹配对
    let mut pairs = get_crossunder_over_pairs(&crossunders, &crossovers);
    for (idx, p) in pairs.iter().enumerate() {
        trace!("Pair[{idx}] {:?}", p);
    }

    let mut valleys = Vec::new();
    for (crossunder_index, crossover_index) in pairs.iter() {
        if
            let Some(lowest_kline_index) = find_lowest(
                all_klines,
                crossunder_index.1,
                crossover_index.1,
                None
            )
        {
            let nearest_crossover_left1 = find_nearest_crossover_left(
                &crossovers,
                &lowest_kline_index
            );

            let nearest_crossunder_right1 = find_nearest_crossunder_right(
                &crossunders,
                &lowest_kline_index
            );

            trace!(
                "\nValley_ left={:?} lowest={:?}, right={:?}",
                nearest_crossover_left1,
                lowest_kline_index,
                nearest_crossunder_right1
            );

            if let (Some(left), Some(right)) = (nearest_crossover_left1, nearest_crossunder_right1) {
                let left_highest = find_highest(all_klines, left.1, lowest_kline_index.1);
                let right_highest = find_highest(all_klines, lowest_kline_index.1, right.1);

                trace!(
                    "Valley~ left_highest={:?} lowest={:?}, right_highest={:?}",
                    left_highest,
                    lowest_kline_index,
                    right_highest
                );

                if let (Some(lhst), Some(rhst)) = (left_highest, right_highest) {
                    let left_highest = all_klines[lhst.1].clone();
                    let lowest = all_klines[lowest_kline_index.1].clone();
                    let right_highest = all_klines[rhst.1].clone();

                    // 计算当前K线的低点与左右两侧最高点的高点之间的百分比变化
                    let left_percent = round2(
                        ((left_highest.high - lowest.low) / lowest.low) * 100.0
                    );

                    // (kline1.close - kline2.close) / kline2.close * 100.0 / (kline1.klts - kline2.klts)
                    // 公式可以更好地反映价格变动的相对强度。这种方法实际上是将价格变动的百分比变化率作为斜率的一部分，
                    // 从而放大了价格变动的影响。这样做可以使角度更有区分度，从而更好地反映价格变动的程度。
                    // 这样，斜率不仅反映了价格变动的程度，还考虑了时间间隔，从而使得角度更有意义。
                    let left_slope =
                        (((left_highest.high - lowest.low) / lowest.low) * 100.0) /
                        ((lowest_kline_index.1 - lhst.1) as f64);
                    let left_slope = angle_degrees(left_slope);

                    let right_percent = round2(
                        ((right_highest.high - lowest.low) / lowest.low) * 100.0
                    );
                    let right_slope =
                        (((right_highest.high - lowest.low) / lowest.low) * 100.0) /
                        ((rhst.1 - lowest_kline_index.1) as f64);
                    let right_slope = angle_degrees(right_slope);

                    let is_valley = if
                        (left_percent >= min_threshold && right_percent >= max_threshold) ||
                        (left_percent >= max_threshold && right_percent >= min_threshold)
                    {
                        true
                    } else {
                        false
                    };

                    trace!(
                        "Valley+ left_highest={:?}({}%,{}) lowest={:?}, right_highest={:?}({}%,{}), {}",
                        left_highest.id_date(),
                        left_percent,
                        left_slope,

                        lowest_kline_index,

                        right_highest.id_date(),
                        right_percent,
                        right_slope,
                        is_valley
                    );

                    if is_valley {
                        valleys.push(KlineValley {
                            id: Some(Thing::from(("kline_valley", lowest.id_raw().as_str()))),
                            left_most: all_klines[left.1].clone(),
                            left_highest,
                            left_percent: round2(left_percent),
                            left_slope,
                            lowest,
                            right_most: all_klines[right.1].clone(),
                            right_highest,
                            right_percent: round2(right_percent),
                            right_slope,
                            is_support: true,
                        });
                    }
                }
            }
        } else {
            trace!("No lowest point found between crossunder and crossover indices.");
        }
    }

    valleys.reverse();

    info!(
        "Found Valleys: left_most, left_highest(high,percent,slope), lowest(low), right_highest(high,percent,slope), right_most "
    );
    for (idx, valley) in valleys.iter().enumerate() {
        info!(
            "Valley[{idx}] {} [{:?}({:.2} {:.2}% {:.2}), {:?}({:.2}~{:.2}), {:?}({:.2} {:.2}% {:.2})] {}",
            valley.left_most.id_date(),

            valley.left_highest.id_date(),
            valley.left_highest.high,
            valley.left_percent,
            valley.left_slope,

            valley.lowest.id_date(),
            valley.lowest.high,
            valley.lowest.low,

            valley.right_highest.id_date(),
            valley.right_highest.high,
            valley.right_percent,
            valley.right_slope,

            valley.right_most.id_date()
        );
    }

    info!("\n");
    valleys
}

#[cfg(test)]
mod test {
    use fudata::db;
    use fudata::model::kline::Klt;
    use log::trace;
    use crate::qtrade::strategy::util::valley_klines::find_valleys;

    #[tokio::test]
    async fn test_find_valleys() {
        // Initialize the logger for the test context.
        // pretty_env_logger::try_init().unwrap();
        pretty_env_logger::init_timed();
        let _ = db::init().await.unwrap();

        let mut all_klines = db::kline
            ::select("SZ.STK.000850", None, Some(&Klt::DAY), Some("klts DESC"), 0, 1250).await
            .unwrap();
        all_klines.sort_by_key(|k| k.klts);

        trace!(
            "all_klines.len()={} \nfirst:{:?} \nlast: {:?}",
            all_klines.len(),
            all_klines.first(),
            all_klines.last()
        );

        let valleys = find_valleys(&all_klines, 30.0, 20.0);

        trace!("\nFound Valleys:");
        for (idx, valley) in valleys.iter().enumerate() {
            trace!(
                "\nValley[{idx}]{}[ {:?}({} {}% {}) {:?}({}) {:?}({} {}% {}) ]{}",
                valley.left_most.id_raw(),

                valley.left_highest.id_raw(),
                valley.left_highest.high,
                valley.left_percent,
                valley.left_slope,

                valley.lowest.id_raw(),
                valley.lowest.low,

                valley.right_highest.id_raw(),
                valley.right_highest.high,
                valley.right_percent,
                valley.right_slope,
                valley.right_most.id_raw()
            );
        }
        trace!("\n");
    }
}
